from enum import Enum

from qtpy import QtWidgets
from qtpy import QtGui
from qtpy import QtCore

from label import Shape
from utils import DrawType
from utils import calc_distance

CURSOR_DRAW = QtCore.Qt.CrossCursor
CURSOR_DEFAULT = QtCore.Qt.ArrowCursor
CURSOR_MOVE = QtCore.Qt.ClosedHandCursor


class DrawMode(Enum):
    DRAWING_TYPE = 0  # 画
    EDITING_TYPE = 1  # 编辑


class Canvas(QtWidgets.QWidget):

    # 定义缩放图片信号
    zoomChangeSignal = QtCore.Signal(int, QtCore.QPoint)
    # 定义标注完成信号
    drawCompletedSignal = QtCore.Signal()
    # 修改了标注形状
    modifyShapeSignal = QtCore.Signal()

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        self.pixmap = QtGui.QPixmap()
        self.painter = QtGui.QPainter()
        # 缩放比例
        self.scale = 1.0
        # 当前的模式
        self.draw_model = DrawMode.EDITING_TYPE.value
        # 当前标注的类型
        self.draw_type = DrawType.DRAW_TYPE_NONE.value
        # 当前鼠标位置
        self.currentPoint = QtCore.QPoint()
        # 平移坐标
        self.translation = QtCore.QPointF(0, 0)
        # 当前标注对象
        self.current_shape = None
        # 当前标注信息
        self.current_labelinfo = None
        # 高亮对象
        self.highlight_shape = None
        # 选择的对象列表
        self.selected_shapes = []
        # 标注对象列表
        self.shape_list = []
        # 前一次鼠标按下的坐标 平移相关(平移的起始位置)
        self.prevMousePressPoint = QtCore.QPoint(0, 0)
        # # 平移的偏移量
        # self.panOffset = QtCore.QPoint(0, 0)

        # 窗体默认设置为不追踪。只有当鼠标按键按下时，鼠标移动时mousePressEvent才影响
        self.setMouseTracking(True)
        self.setFocusPolicy(QtCore.Qt.WheelFocus)
        # 最小距离
        self.min_dis = 10.0

    # 自定义函数
    def undoLastLine(self):
        if self.shape_list:
            self.current_shape = self.shape_list.pop()
            self.current_shape.setOpen()
            if self.draw_type == DrawType.DRAW_TYPE_RECT.value:
                self.current_shape.point_list = self.current_shape.point_list[0:1]
            else:
                # TODO
                pass
        else:
            print("shape_list is empty")

    def getLastLabel(self):
        return self.shape_list[-1]
    # def setLastLabel(self, labelinfo):
    #     # 保存标签信息
    #     self.shape_list[-1].label = labelinfo["name"]
    #     self.shape_list[-1].attributes = labelinfo["attributes"]
    #     self.shape_list[-1].color = labelinfo["color"]
    #     # TODO save label info
    #     return self.shape_list[-1]

    def setEditing(self, value=True):
        if value:
            self.draw_model = DrawMode.EDITING_TYPE.value
        else:
            self.draw_model = DrawMode.DRAWING_TYPE.value

    def setDrawingType(self, value):

        self.draw_type = value

    def isDrawing(self):
        # 判断是不是标注模式
        return self.draw_model == DrawMode.DRAWING_TYPE.value

    def isEditing(self):
        # 判断是不是编辑模式
        return self.draw_model == DrawMode.EDITING_TYPE.value

    def setPixmap(self, pixmap, clear_shapes=True):
        self.pixmap = pixmap
        if clear_shapes:
            self.shape_list = []
        self.update()

    def calcTranslation(self,old_scale,new_scale,mouse_pos):
        # 计算新的偏移量
        old_offset = self.translation
        # new_offset_x = mouse_pos.x() * old_scale - mouse_pos.x() * new_scale
        # new_offset_y = mouse_pos.y() * old_scale - mouse_pos.y() * new_scale
        new_offset_x = (mouse_pos.x() - old_offset.x()) * (1 - new_scale / old_scale)
        new_offset_y = (mouse_pos.y() - old_offset.y()) * (1 - new_scale / old_scale)
        self.translation += QtCore.QPointF(new_offset_x, new_offset_y)

    def setScale(self, scale):
        """设置缩放比例"""
        self.scale = scale
        self.repaint()

    def getScale(self):
        return self.scale

    def zoom_in(self, scaling=1.1):
        self.setScale(self.getScale() * scaling)

    def zoom_out(self, scaling=0.9):
        self.setScale(self.getScale() * scaling)

    def offsetToCenter(self):

        s = self.getScale()
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QtCore.QPointF(x, y)




    def transformPos(self, point):

        return point / self.scale - self.translation


    def updateStatusBar(self, point):
        pos = point
        window = self.parent().window()
        if window.current_image is not None:
            in_pixmap = self.isInPixmap(pos)
            if in_pixmap:
                pixel = window.current_image.pixel(pos.x(), pos.y())

                r = (pixel >> 16) & 0xff
                g = (pixel >> 8) & 0xff
                b = (pixel) & 0xff

                w = self.pixmap.width()
                h = self.pixmap.height()
                x = pos.x()
                y = pos.y()
                text = "RGB=[%d,%d,%d] WH=[%d,%d] XY=[%d,%d]" % (r, g, b, w, h, x, y)
                self.parent().window().status_messages.setText(text)
            else:
                self.parent().window().status_messages.setText("")

    def overrideCursor(self, cursor):
        """设置鼠标的图形"""
        QtWidgets.QApplication.setOverrideCursor(cursor)

    def restoreCursor(self):
        QtWidgets.QApplication.restoreOverrideCursor()

    def setCursor(self,):
        cursor = CURSOR_DEFAULT
        if self.draw_model == DrawMode.DRAWING_TYPE.value:
            if self.draw_type == DrawType.DRAW_TYPE_POINT.value:
                cursor = CURSOR_DRAW
            elif self.draw_type == DrawType.DRAW_TYPE_RECT.value:
                cursor = CURSOR_DRAW
            elif self.draw_type == DrawType.DRAW_TYPE_POLYGON.value:
                cursor = CURSOR_DRAW
        else:
            cursor = CURSOR_DEFAULT
        self.overrideCursor(cursor)

    # def focusOutEvent(self, ev):
    #     """控件失去焦点时的事件"""

    # def focusInEvent(self, ev):
    #     """控件获取焦点时的事件"""

    def leaveEvent(self, ev):
        """当鼠标指针离开一个窗口或控件时的事件"""

        self.overrideCursor(CURSOR_DEFAULT)

    def enterEvent(self, ev):
        """当鼠标指针进入一个窗口或控件的事件"""
        self.setCursor()

    def isInPixmap(self, pos):
        w, h = self.pixmap.width(), self.pixmap.height()
        return (0 <= pos.x() <= w - 1 and 0 <= pos.y() <= h - 1)

    def drawCrossLine(self, painter):
        """绘制十字线"""
        painter.setPen(QtGui.QColor(0, 0, 0))
        painter.drawLine(
            0,
            int(self.currentPoint.y()),
            self.pixmap.width() - 1,
            int(self.currentPoint.y()),
        )
        painter.drawLine(
            int(self.currentPoint.x()),
            0,
            int(self.currentPoint.x()),
            self.pixmap.height() - 1,
        )

    def drawCompleted(self):
        """当前标注完成"""
        self.shape_list[-1].setClose()
        self.current_shape = None
        # 发送完成信号
        self.drawCompletedSignal.emit()

    def cancelHighlight_shape(self):
        if self.highlight_shape:
            self.highlight_shape.cancelHighlightVertex()
            self.update()
            self.highlight_shape = None

    def move_shape(self, event, pos):
        if event.buttons() & QtCore.Qt.LeftButton:
            # 移动点
            if self.highlight_shape and self.highlight_shape.selectVertex():
                self.boundedMoveVertex(pos)
                self.repaint()
                self.modifyShapeSignal.emit()
            elif self.highlight_shape and not self.highlight_shape.selectVertex() and self.prevMousePressPoint:
                # TODO 暂时移动高亮的
                self.boundedMoveShapes(self.highlight_shape, pos)
                self.repaint()
                self.modifyShapeSignal.emit()
                pass
                # self.overrideCursor(CURSOR_MOVE)
            else:
                # TODO
                pass
    
    def boundedMoveShapes(self, shapes, pos):
        # TODO check pos

        offset = pos - self.prevMousePressPoint
        self.prevMousePressPoint = pos
        if isinstance(shapes, list):
            # TODO
            pass
        else:
            shapes.move(offset)

    def intersectingEdges(self, point1, point2, points):
        """Find intersecting edges.

        For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen.
        """
        (x1, y1) = point1
        (x2, y2) = point2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QtCore.QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = calc_distance(m, QtCore.QPointF(x2, y2))
                yield d, i, (x, y)

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        points = [
            (0, 0),
            (size.width() - 1, 0),
            (size.width() - 1, size.height() - 1),
            (0, size.height() - 1),
        ]
        # x1, y1 should be in the pixmap, x2, y2 should be out of the pixmap
        x1 = min(max(p1.x(), 0), size.width() - 1)
        y1 = min(max(p1.y(), 0), size.height() - 1)
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QtCore.QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QtCore.QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QtCore.QPointF(x, y)

    def boundedMoveVertex(self, pos):
        shape = self.highlight_shape
        index = self.highlight_shape.highlight_vertex_index
        point = shape[index]
        if not self.isInPixmap(pos):
            pos = self.intersectionPoint(point, pos)
        shape.moveVertex(index, pos - point)

    def selectedShapes(self, pos, multiple_selection_mode):

        for current_shape in self.shape_list:
            if current_shape.containsPoint(pos):
                if current_shape not in self.selected_shapes:
                    if multiple_selection_mode:
                        self.__selectedShapes(current_shape,clear=False)
                    else:
                        self.__selectedShapes(current_shape,clear=True)
                    return

        self.clearSelectedShapes()

    def __selectedShapes(self, shape, clear=True):

        if clear:
            self.clearSelectedShapes()
        shape.selected = True
        self.selected_shapes.append(shape)


    def clearSelectedShapes(self):
        for shape in self.selected_shapes:
            shape.selected = False
        self.selected_shapes = []

    def deleteSelected(self):
        ret = False
        if self.selected_shapes:
            for shape in self.selected_shapes:
                self.shape_list.remove(shape)
            self.selected_shapes = []
            self.update()
            ret = True
        return ret
    def higlightShape(self, pos):
        for current_shape in self.shape_list:
            # 最近的点
            vertex_index = current_shape.nearestVertex(pos, self.min_dis / self.getScale())
            # 最近的边
            index_edge = current_shape.nearestEdge(pos, self.min_dis / self.getScale())
            if vertex_index is not None:
                if self.highlight_shape and self.highlight_shape.selectVertex():
                    self.highlight_shape.cancelHighlightVertex()
                self.highlight_shape = current_shape
                current_shape.highlightVertex(vertex_index)
                self.update()
                break
            # elif index_edge is not None and current_shape.canAddPoint():
            #     break
            elif current_shape.containsPoint(pos):
                if self.highlight_shape and self.highlight_shape.selectVertex():
                    self.highlight_shape.cancelHighlightVertex()
                # self.overrideCursor
                self.highlight_shape = current_shape
                self.update()
                break
            else:
                self.cancelHighlight_shape()

    def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None:
        """鼠标移动事件"""


        pos = self.transformPos(event.localPos())
        self.currentPoint = pos
        # 更新状态栏信息
        self.updateStatusBar(pos)

        if self.isDrawing():
            if not self.current_shape:
                self.repaint()
                return
            # if self.draw_type == DrawType.DRAW_TYPE_RECT.value:
            self.repaint()

        # 移动图形或者点
        self.move_shape(event, pos)
        # 选择图形或者点
        self.higlightShape(pos)

        return super().mouseMoveEvent(event)

    def mousePressEvent(self, ev):
        """鼠标按压事件"""
        pos = self.transformPos(ev.localPos())
    
        if ev.button() == QtCore.Qt.LeftButton:
            if self.isDrawing():
                in_pixmap = self.isInPixmap(pos)
                # 已经创建图形
                if self.current_shape:
                    if in_pixmap:
                        # 进行特殊处理 与边界计算交点
                        # TODO
                        pass
                    if self.draw_type == DrawType.DRAW_TYPE_POLYGON.value:
                        self.current_shape.addPoint(pos)
                        if self.current_shape.isClosed():
                            self.drawCompleted()
                    elif self.draw_type == DrawType.DRAW_TYPE_RECT.value:
                        if len(self.current_shape) == 1:
                            self.current_shape.addPoint(pos)
                            self.drawCompleted()
                        else:
                            # TODO
                            pass
                else:
                    # 未创建图形 且点击在标注范围内
                    if in_pixmap:
                        label_name = self.current_labelinfo["name"]
                        color = self.current_labelinfo["color"]
                        if color is not None:
                            self.current_shape = Shape(label=label_name, color=color, shape_type=self.draw_type)
                        else:
                            self.current_shape = Shape(label=label_name, shape_type=self.draw_type)
                        self.shape_list.append(self.current_shape)
                        self.current_shape.addPoint(pos)
                    else:
                        # TODO
                        pass
                        # 第一个点就不在范围内无法处理
            elif self.isEditing():
                # TODO
                self.prevMousePressPoint = pos
                pressControl = int(ev.modifiers()) == QtCore.Qt.ControlModifier
                self.selectedShapes(pos, pressControl)

                self.repaint()

            else:
                self.prevMousePressPoint = pos
                pass

    def paintEvent(self, enevt: QtGui.QPaintEvent) -> None:
        # 描画
        p = self.painter
        p.begin(self)
        # 抗锯齿
        p.setRenderHint(QtGui.QPainter.Antialiasing)
        p.setRenderHint(QtGui.QPainter.HighQualityAntialiasing)
        p.setRenderHint(QtGui.QPainter.SmoothPixmapTransform)

        p.scale(self.getScale(), self.getScale())
        p.translate(self.translation)
        # p.translate(self.translation)
        p.drawPixmap(0, 0, self.pixmap)
        if (self.isDrawing() and self.currentPoint and self.isInPixmap(self.currentPoint) and self.draw_type == DrawType.DRAW_TYPE_RECT.value):
            self.drawCrossLine(p)
        # show all
        Shape.scale = self.getScale()
        for one_shape in self.shape_list:
            if one_shape.selected or one_shape == self.highlight_shape:
                one_shape.fill = True
            else:
                one_shape.fill = False
            one_shape.paint(p)
        # show current
        if self.current_shape is not None:
            drawing_shape = self.current_shape.copy()
            drawing_shape.addPoint(self.currentPoint)
            drawing_shape.paint(p)

        p.end()
        return super().paintEvent(enevt)

    def wheelEvent(self, ev) -> None:
        """鼠标滑轮滚动事件"""
        # mods = ev.modifiers()
        delta = ev.angleDelta()
        old_scale = self.getScale()
        if delta.y() < 0:
            self.zoom_out()
        else:
            self.zoom_in()
        new_scale = self.getScale()
        self.calcTranslation(old_scale,new_scale,ev.pos())
        self.zoomChangeSignal.emit(delta.y(), ev.pos())

    def loadShapes(self, shapes):
        self.shape_list = list(shapes)
        self.current_shape = None
        self.update()
